require 'net/smtp'

class Range
  def random
    rand * (last - first) + first
  end
end

module BulkMail
  class Task
    def initialize(domain, addrs, opts)
      @domain = domain
      @addrs = addrs
      @opts = opts
    end
    
    # Sends email to all addresses belonging to a domain.
    def run
      servers = BulkMail.smtp_servers_for_domain(@domain)
      raise RuntimeError, "Unknown domain" if servers.empty?

      last_error = nil
      servers.each do |server|
        begin
          process(server)
          return
        rescue => e
          last_error = e
        end
      end
      raise last_error if last_error
    rescue => e
      @addrs.each {|a| report_addr(a, :error, e.message)}
    end
    
    def delay
      d = @opts[:delay]
      sleep((d.class == Range) ? d.random : d)
    end
    
    def send(server, address)
      Net::SMTP.start(server, nil, @opts[:helo], nil, nil, nil) do |smtp|
        smtp.send_message @opts[:message].to_s, @opts[:from], address
        report_addr(address, :success)
      end
    rescue => e
      report_addr(address, :error, e.message)
    end
    
    def process(server)
      done = nil
      sent = nil
      Thread.new do
        begin
          delay
          @addrs.each {|a| send(server, a); sleep 5 + rand * 25}
        rescue => e
          puts e.message
          puts e.backtrace.first
        ensure
          done = true
        end
      end
      # while !done
      #   sleep 1
      # end
    end
    
    @@sent_count = 0
    @@failed_count = 0
    
    def self.sent_count
      @@sent_count
    end
    
    def self.failed_count
      @@failed_count
    end
    
    # Reports success or failure for the recipient.
    def report_addr(addr, status, error = nil)
      Thread.exclusive do
        case status
        when :success:
          @@sent_count += 1
          puts "Sent mail to #{addr}"
          File.open('sent.bulkmail', 'a') {|f| f.puts "#{addr}"}
        when :error:
          @@failed_count += 1
          puts "Failed to send to #{addr}: #{error}"
          File.open('failed.bulkmail', 'a') {|f| f.puts "#{addr}: #{error}"}
        end
      end
    end
  end
end